package com.ruoyi.finance.domain;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

public class FeeDistributor {
    /**
     * 通用：按明细字段占比分摊目标值
     */
    public static <T> void distributeWithCompensation(
            List<T> list,
            BigDecimal totalAmount,
            Function<T, BigDecimal> ratioGetter,
            Function<T, BigDecimal> targetGetter,
            BiConsumer<T, BigDecimal> setter,
            int scale,                      // 分摊保留几位小数
            BigDecimal toleranceThreshold, // 误差容忍值，如 0.01
            boolean adjustDirection // 是否需要修正方向
    ) {
        if (totalAmount == null || totalAmount.compareTo(BigDecimal.ZERO) == 0 || list.isEmpty()) return;

        // 处理没有值的明细，给它们初始化值
        list.forEach(item -> {
            if (Optional.ofNullable(targetGetter.apply(item)).orElse(BigDecimal.ZERO).compareTo(BigDecimal.ZERO) == 0) {
                // 如果为空，则给它一个初始值
                setter.accept(item, BigDecimal.ZERO);
            }
        });

        // 如果需要修正方向，则判断总额符号并进行调整
        if (adjustDirection) {
            BigDecimal totalRatio = list.stream()
                    .map(ratioGetter)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            BigDecimal actualTotal = list.stream()
                    .map(targetGetter)
                    .filter(Objects::nonNull)
                    .reduce(BigDecimal.ZERO, BigDecimal::add);

            if (totalRatio.signum() != actualTotal.signum()) {
                totalAmount = totalAmount.negate();  // 修正方向
            }
        }

        BigDecimal totalRatio = list.stream()
                .map(ratioGetter)
                .filter(Objects::nonNull)
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        if (totalRatio.compareTo(BigDecimal.ZERO) == 0) return;

        BigDecimal actualTotal = BigDecimal.ZERO;

        for (T item : list) {
            BigDecimal ratio = Optional.ofNullable(ratioGetter.apply(item)).orElse(BigDecimal.ZERO);
            BigDecimal share = ratio.multiply(totalAmount)
                    .divide(totalRatio, scale, RoundingMode.HALF_UP); // 初步多保留几位精度
            share = share.setScale(scale, RoundingMode.HALF_UP);         // 再按需求四舍五入
            setter.accept(item, share);
            actualTotal = actualTotal.add(share);
        }

        BigDecimal diff = totalAmount.subtract(actualTotal).setScale(scale, RoundingMode.HALF_UP);

        if (diff.abs().compareTo(toleranceThreshold) >= 0) {
            T maxItem = list.stream()
                    .max(Comparator.comparing(d -> Optional.ofNullable(ratioGetter.apply(d)).orElse(BigDecimal.ZERO)))
                    .orElse(null);

            if (maxItem != null) {
                BigDecimal existing = Optional.ofNullable(targetGetter.apply(maxItem)).orElse(BigDecimal.ZERO);
                setter.accept(maxItem, existing.add(diff));
            }
        }
    }

    /**
     * 通用：按明细字段占比分摊目标值，并更新数据库
     */
    public static <T> void distributeAndUpdate(
            List<WalmartProfitReportDetail> details,
            BigDecimal targetTotal,
            Function<WalmartProfitReportDetail, BigDecimal> getter,
            BiConsumer<WalmartProfitReportDetail, BigDecimal> setter,
            Consumer<WalmartProfitReportDetail> updater, // update函数，比如 mapper::updateById
            int scale,
            BigDecimal tolerance
    ) {
        // 实际值
        BigDecimal actualTotal = details.stream()
                .map(d -> Optional.ofNullable(getter.apply(d)).orElse(BigDecimal.ZERO))
                .reduce(BigDecimal.ZERO, BigDecimal::add);

        BigDecimal diff = targetTotal.subtract(actualTotal);

        if (diff.abs().compareTo(tolerance) <= 0 || actualTotal.compareTo(BigDecimal.ZERO) == 0) {
            return; // 差值在误差范围内，或总金额为0，不处理
        }

        BigDecimal totalAdjusted = BigDecimal.ZERO;

        for (int i = 0; i < details.size(); i++) {
            WalmartProfitReportDetail d = details.get(i);
            BigDecimal origin = Optional.ofNullable(getter.apply(d)).orElse(BigDecimal.ZERO);

            if (i == details.size() - 1) {
                // 最后一条补足误差
                BigDecimal lastAdjust = diff.subtract(totalAdjusted);
                setter.accept(d, origin.add(lastAdjust).setScale(scale, RoundingMode.HALF_UP));
            } else {
                BigDecimal ratio = origin.divide(actualTotal, 10, RoundingMode.HALF_UP);
                BigDecimal adjust = diff.multiply(ratio).setScale(scale, RoundingMode.HALF_UP);
                totalAdjusted = totalAdjusted.add(adjust);
                setter.accept(d, origin.add(adjust).setScale(scale, RoundingMode.HALF_UP));
            }
            updater.accept(d); // ⬅ 更新数据库
        }
    }
}
